{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<style>div.container { width: 100% }</style>\n",
    "<img style=\"float:left;  vertical-align:text-bottom;\" height=\"65\" width=\"172\" src=\"../assets/PyViz_logo_wm_line.png\" />\n",
    "<div style=\"float:right; vertical-align:text-bottom;\"><h2>Tutorial 07. Working with geographic data</h2></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "PyViz is designed to offer a set of general tools that can help visualize all sorts of different types of data, usually without any domain-specific support needed.  However, it does include special handling for the case of visualizing data at geographic locations, because of the sometimes-complex transformations needed for this common case.\n",
    "\n",
    "The GeoViews package provides a library of Element types that extend standard HoloViews functionality by making the elements aware of geographic projections. The plotting code will automatically transform coordinates to the appropriate projection during plotting so you can work with latitudes and longitudes while GeoViews will handle the complexities of projecting data to appropriate coordinates for display. GeoViews makes extensive use of Cartopy, a general-purpose library for working with geographic data, which in turn builds on many lower-level libraries like PROJ.4, GEOS, and GDAL. It also works well with <a href=\"http://geopandas.org\">GeoPandas</a>.\n",
    "\n",
    "<div style=\"margin: 10px\">\n",
    "<a href=\"http://holoviews.org\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:150px\" src=\"../assets/holoviews.png\"/></a>\n",
    "<a href=\"http://geoviews.org\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:150px\" src=\"../assets/geoviews.png\"/></a>\n",
    "<a href=\"http://bokeh.pydata.org\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:150px\" src=\"../assets/bokeh.png\"/></a><br>\n",
    "<a href=\"http://scitools.org.uk/cartopy\"><img style=\"margin:8px; display:inline; object-fit:scale-down; max-height:120px\" src=\"../assets/cartopy.png\"/></a>\n",
    "<a href=\"http://matplotlib.org\"><img style=\"margin:4px; display:inline; object-fit:scale-down; max-height:150px\" src=\"../assets/matplotlib_wm.png\"/></a>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cartopy.crs as ccrs\n",
    "import holoviews as hv\n",
    "import geoviews as gv\n",
    "import geoviews.feature as gf\n",
    "\n",
    "hv.extension('bokeh', 'matplotlib')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Projections\n",
    "\n",
    "The Cartopy project provides a convenient Python wrapper around the Proj.4 library, making it easy to define geographic projections in Python. GeoViews elements accept a ``crs`` parameter declaring the coordinate reference system in which the Element's data is expressed. By default all GeoViews elements assume the ``PlateCarree`` projection, which is an Equirectangular projection with coordinates defined as regular latitudes and longitudes. When plotting geographic data with bokeh, GeoViews will automatically convert the data to WebMercator coordinates, allowing it to be easily overlaid on top of tile sources."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Points [width=600 height=500] (size=6)\n",
    "\n",
    "nyc     = (-74.0, 40.7, 'NYC')\n",
    "london  = (  0.1, 51.5, 'London')\n",
    "beijing = (116.4, 39.9, 'Beijing')\n",
    "\n",
    "points = gv.Points([nyc, london, beijing], vdims=['City'], extents=(-180, -90, 180, 90))\n",
    "print('Default projection:', points.crs.__class__.__name__)\n",
    "points * gf.borders * gf.coastline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When using bokeh it will project the data to Google Mercator coordinates by default however by supplying an explicit ``projection`` we can display the data in another coordinate system, independent of the projection of the original data. Here we switch to the ``Robinson`` projection:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Points [projection=ccrs.Robinson() width=800 height=400 global_extent=True]\n",
    "points * gf.borders * gf.coastline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Projecting data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "GeoViews also provides operations to explicitly project data, which is what the plotting classes use in the background. Here we project our points to the Google Mercator projection and display them as a pandas DataFrame:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "projected = gv.operation.project(points)\n",
    "print('New projection:', projected.crs.__class__.__name__)\n",
    "projected.dframe().set_index('City')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is often useful to project the data ahead of time, particularly for large datasets, because when working interactively we often display the data many times and don't want to reproject it every time.\n",
    "\n",
    "## Tile sources\n",
    "\n",
    "Tile sources are very convenient ways to provide geographic context for a plot and they will be familiar from the popular mapping services like Google Maps and Openstreetmap. The ``WMTS`` element provides an easy way to include such a tile source in your visualization simply by passing it a valid URL template. GeoViews provides a number of useful tile sources in the ``gv.tile_sources`` module:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import geoviews.tile_sources as gts\n",
    "\n",
    "gv.Layout([ts.relabel(name) for name, ts in gts.tile_sources.items()]).options(\n",
    "    'WMTS', xaxis=None, yaxis=None, width=225, height=225\n",
    ").cols(4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try zooming and panning to explore what data is provided by each of the tile sources.\n",
    "\n",
    "\n",
    "## Geometries\n",
    "\n",
    "#### Features\n",
    "\n",
    "As we have already discovered, GeoViews ships with a number of geographic features available under ``geoviews.feature``, which we have imported as ``gf``. Additionally we may load other ``NaturalEarthFeature``s that can be loaded using ``cartopy``:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Feature [width=600 height=500 global_extent=True]\n",
    "import cartopy.feature as cf\n",
    "graticules = cf.NaturalEarthFeature(\n",
    "    category='physical',\n",
    "    name='graticules_30',\n",
    "    scale='110m')\n",
    "gf.ocean * gf.land * gf.coastline * gv.Feature(graticules, group='Lines')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Geopandas\n",
    "\n",
    "GeoPandas extends the datatypes used by pandas to allow spatial operations on geometric types, which makes it a very convenient way of working with geometries with associated variables. A GeoPandas dataframe acts just like a pandas DataFrame except for the addition of a ``geometry`` column that holds shapely geometries, along with metadata."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import geopandas as gpd\n",
    "world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))\n",
    "world.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "GeoViews ``Path``, ``Contours`` and ``Polygons`` Elements natively support projecting and plotting of\n",
    "geopandas DataFrames using both ``matplotlib`` and ``bokeh`` plotting extensions. We will load the example dataset of the world which also includes some additional data about each country, to be revealed by hovering:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Polygons [width=600 height=500 tools=['hover']] (cmap='viridis')\n",
    "gv.Polygons(world, vdims=['gdp_md_est', 'continent', 'name']).redim.range(Latitude=(-60, 90))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Shapes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``gv.Shape`` object wraps around any shapely geometry, allowing finer grained control over each polygon. We can, for example select one particular geometry in the geopandas dataframe and display it separately by wrapping it in the ``Shape`` element:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "usa = world[world.name=='United States'].geometry.iloc[0]\n",
    "gv.Shape(usa)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Onwards\n",
    "\n",
    "See [geoviews.org](http://geoviews.org) and the [EarthSim](https://earthsim.pyviz.org) site for more details on geographic tools that work well in PyViz.\n",
    "\n",
    "Meanwhile, we now go on to show dynamic interactions, starting with [Custom Interactivity](./08_Custom_Interactivity)"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
